#include "mysql_base.h"
#include <assert.h>
#include <spdlog/spdlog.h>
#include <spdlog/fmt/fmt.h>
#include <charconv>

int MysqlClient::client_count_{0};
std::mutex MysqlClient::client_count_mutex_;
std::unordered_map<pid_t, int> MysqlClient::thread_client_count_;


std::unordered_map<MysqlError, const char *> MysqlException::mysql_error_msgs_
{
    {MysqlError::Success, "success"},
    {MysqlError::InitLibraryFail, "init library fail"},
    {MysqlError::InitThreadFail, "init thread fail"},
    {MysqlError::InitFail, "init fail"},
    {MysqlError::ConnectFail, "connect fail"},
    {MysqlError::QueryFail, "query fail"},
    {MysqlError::UpdateFail, "update fail"},
    {MysqlError::DeleteFail, "delete fail"},
    {MysqlError::CharacterSetError, "character set error"},
};

MysqlQueryResult::MysqlQueryResult(MYSQL_RES *res)
{
    parse_field(res);
    parse_result(res);
}

const std::vector<std::string_view> &MysqlQueryResult::column_names() const
{
    return fields_;
}

// int MysqlQueryResult::column_index(std::string_view column_name) const
// {
//     auto iter = column_index_.find(column_name.data());
//     if (iter == column_index_.end()) {
//         assert(false && fmt::format("column {} not found", column_name).c_str());
//         return -1;
//     }
//     return iter->second;
// }

int MysqlQueryResult::row_count() const
{
    return result_.size();
}

const std::vector<DBRow> &MysqlQueryResult::result() const
{
    return result_;
}

void MysqlQueryResult::parse_field(MYSQL_RES *res)
{
    MYSQL_FIELD *field;
    auto num_fields = mysql_num_fields(res);
    fields_.reserve(num_fields);
    int i = 0;
    while ((field = mysql_fetch_field(res))) {
        fields_.push_back(field->name);
        column_index_[field->name] = i;
        ++i;
    }
}

void MysqlQueryResult::parse_result(MYSQL_RES *res)
{
    MYSQL_ROW row;
    auto num_rows = mysql_num_rows(res);
    result_.reserve(num_rows);
    while ((row = mysql_fetch_row(res))) {
        std::vector<std::string_view> row_result;
        row_result.reserve(fields_.size());
        for (size_t i = 0; i < fields_.size(); ++i) {
            row_result.push_back(row[i] ? row[i] : "");
        }
        result_.push_back(DBRow(column_index_, std::move(row_result)));
    }
    printf("parse result done\n");
    assert(result_.size() == num_rows);
}

void MysqlClient::init()
{
    std::lock_guard<std::mutex> lck(client_count_mutex_);
    if (0 == client_count_) {
        if (mysql_library_init(0, nullptr, nullptr)) {
            throw MysqlException(MysqlError::InitLibraryFail);
        }
    }
    auto tid = gettid();
    if ((thread_client_count_.find(tid) == thread_client_count_.end()) or (0 == thread_client_count_[tid])) {
        mysql_thread_init();
        thread_client_count_[tid] = 1;
    }
    else {
        ++thread_client_count_[tid];
    }
    ++client_count_;
}

void MysqlClient::destroy()
{
    std::lock_guard<std::mutex> lck(client_count_mutex_);
    --client_count_;
    if (0 == client_count_) {
        mysql_library_end();
    }
    auto tid = gettid();
    --thread_client_count_[tid];
    if (0 == thread_client_count_[tid]) {
        mysql_thread_end();
    }
    assert(client_count_ >= 0 and thread_client_count_[tid] >= 0);
}


MysqlClient::MysqlClient(std::string_view host,
                         uint16_t port,
                         std::string_view username,
                         std::string_view passwd,
                         std::string_view schema)
{
    init();
    conn_ = mysql_init(nullptr);
    if (!mysql_real_connect(conn_,
            host.data(),
            username.data(),
            passwd.data(),
            schema == "" ? nullptr : schema.data(),
            port,
            nullptr,
            CLIENT_FOUND_ROWS
            )) {
        SPDLOG_ERROR("{}", mysql_error(conn_));
        throw MysqlException(MysqlError::ConnectFail);
    }
    if (!mysql_set_character_set(conn_, "utf8mb4")) {
        SPDLOG_INFO("New client character set: {}",
               mysql_character_set_name(conn_));
    }
    else {
        throw MysqlException(MysqlError::CharacterSetError);
    }
}

MysqlClient::~MysqlClient()
{
    destroy();
}

void MysqlClient::query(std::string_view sql)
{
    mysql_query(conn_, sql.data());
    auto result = mysql_store_result(conn_);
    if (!result) {
        SPDLOG_ERROR("{}", mysql_error(conn_));
        throw MysqlException(MysqlError::QueryFail);
    }
    result_ptr_ = std::make_shared<MysqlQueryResult>(result);
}

const std::shared_ptr<DBQueryResult> MysqlClient::result() const
{
    return result_ptr_;
}
